package net.java.nioserver.sio;

import net.java.nioserver.utils.ChannelUtil;
import net.java.nioserver.BasicService;
import net.java.nioserver.BasicServiceBuilder;
import net.java.nioserver.IoLogUtil;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

import java.io.IOException;
import java.net.ServerSocket;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.AsynchronousCloseException;
import java.util.concurrent.TimeUnit;

/**
 * @author Leonid Shlyapnikov
 */
public class SioService extends BasicService {

    private static final Logger log = LoggerFactory.getLogger(SioService.class);

    private volatile boolean isRunning = false;

    private ServerSocketChannel ssc;

    protected SioService(BasicServiceBuilder builder) {
        super(builder);
    }

    public boolean isRuning() {
        return isRunning;
    }

    public void start() throws IOException {
        init();
        runMainLoop();
    }

    private void init() throws IOException {
        ssc = ServerSocketChannel.open();
        ssc.configureBlocking(true);
        ServerSocket ss = ssc.socket();
        ss.bind(address);
    }

    private void runMainLoop() {
        isRunning = true;

        log.info("Started " + this);

        while (isRunning) {
            try {
                acceptNewClientConnection();
            } catch (AsynchronousCloseException e) {
                IoLogUtil.logAsynchronousCloseException(e, ssc, log);
            } catch (Exception e) {
                log.error("Accept client connection error", e);
            }
        }

        log.info("Stopping " + this + "...");
    }

    private void acceptNewClientConnection() throws IOException {
        SocketChannel sc = ssc.accept();
        executor.execute(new SioTask(opAccept, opRead, sc));
    }

    public synchronized void stop(long millisToWait) {
        if (!isRunning) {
            log.info("The service is already shutdown " + this);
            return;
        }

        log.info("Shutting down " + this);

        isRunning = false;

        if (null != executor) {
//            executor.shutdownNow();
            executor.shutdown();
            try {
                executor.awaitTermination(millisToWait, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                log.warn("Interrupted while waiting for executor shutdown", e);
                Thread.currentThread().interrupt();
            }
        }

        // TODO implement closing of all client connections

        ChannelUtil.close(ssc);
    }

    public String toString() {
        return this.getClass().getSimpleName() + "{" +
                "address=" + address +
                ", executor=" + executor +
                ", opAccept=" + opAccept +
                ", opRead=" + opRead +
                ", isRunning=" + isRunning +
                '}';
    }


    public static class Builder extends BasicServiceBuilder {
        public BasicService build() {
            validate();
            return new SioService(this);
        }

        public BasicServiceBuilder validate() {
            super.validate();

            if (null == executor) {
                throw new NullPointerException("executor property is not set");
            }

            return this;
        }
    }
}
